/* ************************************************************************ */
/*																			*/
/*  ScreenWeaver for Python													*/
/*  Copyright (c)2009 Andrew Klaptsoff aka Skyggedans, Mach Diamond Labs	*/
/*																			*/
/*  Based on ScreenWeaver HX												*/
/*  Copyright (c)2006 Edwin van Rijkom, Nicolas Cannasse					*/
/*																			*/
/* This library is free software; you can redistribute it and/or			*/
/* modify it under the terms of the GNU Lesser General Public				*/
/* License as published by the Free Software Foundation; either				*/
/* version 2.1 of the License, or (at your option) any later version.		*/
/*																			*/
/* This library is distributed in the hope that it will be useful,			*/
/* but WITHOUT ANY WARRANTY; without even the implied warranty of			*/
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU		*/
/* Lesser General Public License or the LICENSE file for more details.		*/
/*																			*/
/* ************************************************************************ */

#include <stdlib.h>
#include "system.h"
#include "flash_dll.h"
#include "flash.h"
#include "python.h"

#ifdef _MSC_VER
#	include <crtdbg.h>
#else
#	define _CrtSetDbgFlag(x)
#endif

flash_dll *fl_dll = NULL;
static int in_call = 0;
static PyObject *application, *windows = NULL;

#ifdef SWPY_WINDOWS
#define SWM_SYNC_CALL WM_USER + 666
#define SWM_INTERRUPT SWM_SYNC_CALL + 1
#define SYNC_CLASS_NAME L"SWPySyncWindow"

static HWND sync_call_hwnd;
static CRITICAL_SECTION sync_call_crit_sect;
static HANDLE sync_call_event;
static DWORD main_thread_id;
#endif

extern void freeNPIds(void);


/* ************************************************************************* */
// STREAM

typedef struct {
    NPP inst;
    NPStream stream;
    uint16 stype;
    int pos;
    int notify;
    flash *f;
} stream;

typedef struct {
    PyObject_HEAD
        stream *s;
} StreamObject;

static int stream_data(stream *s, char *buf, int size) {
    int len = fl_dll->table.writeready(s->inst, &s->stream);
    if (len <= 0)
        return len;
    if (len > size)
        len = size;
    len = fl_dll->table.write(s->inst, &s->stream, s->pos, len, buf);
    s->pos += len;
    return len;
}

static void stream_free(stream *s) {
    // if the app has not yet been closed
    if (fl_dll != NULL) {
        if (s->notify != -1)
            fl_dll->table.urlnotify(s->inst, s->stream.url,
            s->notify ? NPRES_DONE : NPRES_NETWORK_ERR, s->stream.notifyData);
        fl_dll->table.destroystream(s->inst, &s->stream, NPRES_DONE);
    }
    free((char *)s->stream.url);	
}

static int Stream_init(StreamObject *self, PyObject *args) {
    return 0;
}

static PyObject *Stream_close(StreamObject *self, PyObject *args) {
    int success;

#ifdef SWPY_WINDOWS
    if (main_thread_id != GetCurrentThreadId()) {
        PyErr_SetString(PyExc_Exception, "A call was made outside the main thread");
    }
#endif
    if (!PyArg_ParseTuple(args, "i", &success))
        return NULL;
    self->s->notify = success;
    stream_free(self->s);
    Py_RETURN_NONE;
}

static PyObject *Stream_write_char(StreamObject *self, PyObject *args) {
    char c;

#ifdef SWPY_WINDOWS
    if (main_thread_id != GetCurrentThreadId()) {
        PyErr_SetString(PyExc_Exception, "A call was made outside the main thread");
    }
#endif
    if (!PyArg_ParseTuple(args, "c", &c))
        return NULL;
    if(stream_data(self->s, &c, 1) != 1)
        return NULL;
    Py_RETURN_NONE;
}

static PyObject *Stream_write_bytes(StreamObject *self, PyObject *args) {
    char *buf;
    int pos, len;

#ifdef SWPY_WINDOWS
    if (main_thread_id != GetCurrentThreadId()) {
        PyErr_SetString(PyExc_Exception, "A call was made outside the main thread");
    }
#endif
#if PY_MAJOR_VERSION >= 3
    if (!PyArg_ParseTuple(args, "yii", &buf, &pos, &len))
        return NULL;
#else
    if (PyTuple_GET_SIZE(args) < 3)
        return NULL;
    buf = PyString_AsString(PyTuple_GetItem(args, 0));
    if (buf == NULL)
        return NULL;
    pos = PyLong_AsLong(PyTuple_GetItem(args, 1));
    len = PyLong_AsLong(PyTuple_GetItem(args, 2));
#endif
    if(pos < 0 || len < 0 || pos + len > buf)
        return NULL;
    return PyLong_FromLong(stream_data(self->s, buf + pos, len));
}

static PyMethodDef Stream_methods[] = {
    { "close", (PyCFunction)Stream_close, METH_VARARGS, "" },
    { "write_char", (PyCFunction)Stream_write_char, METH_VARARGS, "" },
    { "write_bytes", (PyCFunction)Stream_write_bytes, METH_VARARGS, "" },
    { NULL, NULL } // Sentinel
};

static PyObject *Stream_getattro(StreamObject *self, PyObject *name) {
    PyObject *res;
#if PY_MAJOR_VERSION >= 3
    char *n = _PyUnicode_AsString(name);
#else
    char *n = PyString_AsString(name);
    res = Py_FindMethod(Stream_methods, (PyObject*) self, n);
    if (res)
        return res;
    PyErr_Clear();
#endif
    if (strcmp(n, "size") == 0) 
        res = PyLong_FromLong(self->s->stream.end);
    else {
        //PyErr_SetString(PyExc_AttributeError, name);
        //return NULL;
        return PyObject_GenericGetAttr(self, name);
    }
    Py_INCREF(res);
    return res;
}

static int Stream_setattro(StreamObject *self, PyObject *name, PyObject *value) {
#if PY_MAJOR_VERSION >= 3
    char *n = _PyUnicode_AsString(name);
#else
    char *n = PyString_AsString(name);
#endif
    if (strcmp(n, "size") == 0) {
        if (!PyLong_Check(value))
            return -1;
        self->s->stream.end = PyLong_AsLong(value);
    }
    return 0;
}

static PyTypeObject Stream_Type = {
    PyVarObject_HEAD_INIT(NULL, 0)
    "_swpy.Stream",                 /* tp_name */
    sizeof(StreamObject),           /* tp_basicsize */
    0,                              /* tp_itemsize */
    0,                              /* tp_dealloc */
    0,                              /* tp_print */
    0,                              /* tp_getattr */
    0,                              /* tp_setattr */
    0,                              /* tp_reserved */
    0,                              /* tp_repr */
    0,                              /* tp_as_number */
    0,                              /* tp_as_sequence */
    0,                              /* tp_as_mapping */
    0,                              /* tp_hash */
    0,                              /* tp_call */
    0,                              /* tp_str */
    (getattrofunc)Stream_getattro,  /* tp_getattro */
    (setattrofunc)Stream_setattro,  /* tp_setattro */
    0,                              /* tp_as_buffer */
    Py_TPFLAGS_DEFAULT,             /* tp_flags */
    PyDoc_STR("Stream object"),     /* tp_doc */
    0,                              /* tp_traverse */
    0,                              /* tp_clear */
    0,                              /* tp_richcompare */
    0,                              /* tp_weaklistoffset */
    0,                              /* tp_iter */
    0,                              /* tp_iternext */
    Stream_methods,                 /* tp_methods */
    0,                              /* tp_members */
    0,                              /* tp_getset */
    0,                              /* tp_base */
    0,                              /* tp_dict */
    0,                              /* tp_descr_get */
    0,                              /* tp_descr_set */
    0,                              /* tp_dictoffset */
    (initproc)Stream_init,          /* tp_init */
    (allocfunc)PyType_GenericNew,   /* tp_alloc */
    (newfunc)PyType_GenericNew,     /* tp_new */
    (freefunc)PyObject_Del,         /* tp_free */
};

/* ************************************************************************* */
// FLASH

typedef struct {
    PyObject_HEAD
        flash *f;
    PyObject *w;
    PyObject *oncall;
    PyObject *onurl;
} FlashObject;

static const char *flash_on_call_event( flash *f, const char *ident,
                                       const char *params, int *size ) {
    FlashObject *self = (FlashObject *)flashp_get_private(f);
    PyObject *args, *r;
    PyGILState_STATE gs;
    char *res;

    if(self->oncall == NULL)
       return NULL;
    in_call++;
#ifdef SWPY_WINDOWS
    ResetEvent(sync_call_event);
#endif
    gs = PyGILState_Ensure();
    args = Py_BuildValue("(Osy)", self, ident, params);
    r = PyObject_CallObject(self->oncall, args);
    in_call--;
    if ((r == NULL) || !PyBytes_Check(r)) {
        PyErr_SetString(PyExc_Exception, "Result should be a bytes type");
        return NULL;
    }
    r = PyUnicode_FromFormat("<string>%s</string>", PyBytes_AsString(r));
    if (size != 0)
       *size = PyUnicode_GetSize(r);
    res = _PyUnicode_AsString(r);
    PyGILState_Release(gs);
#ifdef SWPY_WINDOWS
    SetEvent(sync_call_event);
#endif
    return res;
}

// called by NP_HOST
void flashp_url_process(flash *f, const char *url, const char *postData,
    int postLen, void *notifyData) {
    stream *s = (stream *)malloc(sizeof(stream));
    int success;
    FlashObject *self = (FlashObject *)flashp_get_private(f);
    StreamObject *ps;

    memset( &s->stream, 0, sizeof( NPStream ) );
    s->inst = flashp_get_npp(f);
    s->stream.notifyData = notifyData;
    s->stream.url = _strdup(url);
    s->pos = 0;
    s->notify = -1;
    success = (fl_dll->table.newstream(s->inst, FLASH_MIME, &s->stream, 0,
        &s->stype) == NPERR_NO_ERROR);
    if(self->onurl == NULL)
        success = 0;
    if(!success) {
        fl_dll->table.urlnotify(s->inst, url, NPRES_NETWORK_ERR, notifyData);
        return;
    }
    ps = (StreamObject *)PyObject_New(StreamObject, &Stream_Type);
    if (ps != NULL) {
        ps->s = s;
        if (!postData)
            Py_IncRef(Py_None);
        PyObject_CallObject(self->onurl, Py_BuildValue("(OOsO)", self, ps, url,
            postData ? PyUnicode_FromString(postData) : Py_None));
        Py_XDECREF(ps);
    }
}

static int Flash_init(FlashObject *self, PyObject *args) {
    return 0;
}

static void Flash_dealloc(FlashObject *self) {
    Py_XDECREF(self->oncall);
    Py_XDECREF(self->onurl);
    Py_TYPE(self)->tp_free((PyObject *)self);
}

static PyObject *Flash_start(FlashObject *self) {
    if (!flashp_start(self->f))
        return NULL;
    Py_RETURN_NONE;
}

static PyObject *Flash_call(FlashObject *self, PyObject *args) {
    char *r, *func, *params;
    PyObject *v;
#ifdef SWPY_WINDOWS
    if (main_thread_id != GetCurrentThreadId()) {
        PyErr_SetString(PyExc_Exception, "A call was made outside the main thread");
    }
#endif
    if (!PyArg_ParseTuple(args, "sy", &func, &params))
        return NULL;
    r = flashp_call_in(self->f, func, params);
    if( r == NULL ) {
        //PyErr_SetString(PyExc_Exception, "A call was made outside the main loop");
        return NULL;
    }
    v = PyBytes_FromString(r);
    free(r);
    return v;
}

static PyMethodDef Flash_methods[] = {
    { "start", (PyCFunction)Flash_start, METH_NOARGS, "" },
    { "call", (PyCFunction)Flash_call, METH_VARARGS, "" },
    { NULL, NULL } // Sentinel
};

static PyObject *Flash_getattro(FlashObject *self, PyObject *name) {
    PyObject *res = NULL;
#if PY_MAJOR_VERSION >= 3
    char *n = _PyUnicode_AsString(name);
#else
    char *n = PyString_AsString(name);
    res = Py_FindMethod(Flash_methods, (PyObject*) self, n);
    if (res)
        return res;
    PyErr_Clear();
#endif
    if (strcmp(n, "on_call") == 0) 
        res = self->oncall;
    else if (strcmp(n, "on_url") == 0) 
        res = self->onurl;
    else if (strcmp(n, "src") == 0)
        res = PyUnicode_FromString(flashp_get_attribute(self->f, n));
    else {
        return PyObject_GenericGetAttr(self, name);
        //PyErr_SetString(PyExc_AttributeError, name);
        //return NULL;
    }
    Py_INCREF(res);
    return res;
}

static int Flash_setattro(FlashObject *self, PyObject *name, PyObject *value) {
#if PY_MAJOR_VERSION >= 3
    char *n = _PyUnicode_AsString(name);
#else
    char *n = PyString_AsString(name);
#endif
    PyObject *f = value == Py_None ? NULL : value;
    if (strcmp(n, "on_call") == 0) {
        if (!PyCallable_Check(f))
            return -1;
        Py_XDECREF(self->oncall);
        self->oncall = f;
        Py_INCREF(self->oncall);
    }
    else if (strcmp(n, "on_url") == 0) {
        if (!PyCallable_Check(f))
            return -1;
        Py_XDECREF(self->onurl);
        self->onurl = f;
        Py_INCREF(self->onurl);
    }
    else {
        if (!PyUnicode_Check(value))
            return -1;
#if PY_MAJOR_VERSION >= 3
        flashp_set_attribute(self->f, n, _PyUnicode_AsString(value));
#else
        flashp_set_attribute(self->f, n, PyString_AsString(value));
#endif
    }
    return 0;
}

static PyTypeObject Flash_Type = {
    PyVarObject_HEAD_INIT(NULL, 0)
    "_swpy.Flash",            	    /* tp_name */
    sizeof(FlashObject),      	    /* tp_basicsize */
    0,                        	    /* tp_itemsize */
    (destructor)Flash_dealloc,	    /* tp_dealloc */
    0,                        	    /* tp_print */
    0,                        	    /* tp_getattr */
    0,                        	    /* tp_setattr */
    0,                        	    /* tp_reserved */
    0,                        	    /* tp_repr */
    0,                        	    /* tp_as_number */
    0,                        	    /* tp_as_sequence */
    0,                        	    /* tp_as_mapping */
    0,                        	    /* tp_hash */
    0,                        	    /* tp_call */
    0,                        	    /* tp_str */
    (getattrofunc)Flash_getattro,   /* tp_getattro */
    (setattrofunc)Flash_setattro,	/* tp_setattro */
    0,								/* tp_as_buffer */
    Py_TPFLAGS_DEFAULT,       	    /* tp_flags */
    PyDoc_STR("_swpy.Flash object"),	    /* tp_doc */
    0,                        	    /* tp_traverse */
    0,                        	    /* tp_clear */
    0,                        	    /* tp_richcompare */
    0,                        	    /* tp_weaklistoffset */
    0,                        	    /* tp_iter */
    0,                        	    /* tp_iternext */
    Flash_methods,             	    /* tp_methods */
    0,                        	    /* tp_members */
    0,                        	    /* tp_getset */
    0,                        	    /* tp_base */
    0,                        	    /* tp_dict */
    0,                        	    /* tp_descr_get */
    0,                        	    /* tp_descr_set */
    0,                        	    /* tp_dictoffset */
    (initproc)Flash_init,      	    /* tp_init */
    (allocfunc)PyType_GenericAlloc, /* tp_alloc */
    (newfunc)PyType_GenericNew,     /* tp_new */
    (freefunc)PyObject_Del,    	    /* tp_free */
};

/* ************************************************************************* */
// WINDOW

typedef struct {
    PyObject_HEAD
        window *w;
    FlashObject *f;
    PyObject *ondestroy;
    PyObject *onclose;
    PyObject *onminimize;
    PyObject *onmaximize;
    PyObject *onrightclick;
    PyObject *onfilesdropped;
    PyObject *onrestore;
} WindowObject;

static int window_on_event( window *w, enum WindowEvent e, void *data ) {
    WindowObject *self = (WindowObject *)system_window_get_private(w);
    string_list *sl;
    PyObject *arg;
    int i, result = 0;
    PyGILState_STATE gs;

    if( self == NULL )
        return 0;

    gs = PyGILState_Ensure();

    switch( e ) {
        case WE_CLOSE:
            if( self->onclose )
                result = PyObject_IsTrue(PyObject_CallObject(self->onclose,
                Py_BuildValue("(O)", self)));
            break;
        case WE_DESTROY:
            i = PySequence_Index(windows, (PyObject *)self);
            if (i >= 0)
                PySequence_DelItem(windows, i);
            flashp_destroy(self->f->f);
            Py_XDECREF(self->f);
            system_window_set_prop(w, WP_FLASH_RUNNING, 0);
            if (self->ondestroy)
                PyObject_CallObject(self->ondestroy, Py_BuildValue("(O)", self));
            // Remove from window list
            Py_XDECREF(self);
            break;
        case WE_MINIMIZE:
            if( self->onminimize )
                result = PyObject_IsTrue(PyObject_CallObject(self->onminimize,
                Py_BuildValue("(O)", self)));
            break;
        case WE_MAXIMIZE:
            if( self->onmaximize )
                result = PyObject_IsTrue(PyObject_CallObject(self->onmaximize,
                Py_BuildValue("(O)", self)));
            break;
        case WE_RIGHTCLICK:
            if( self->onrightclick )
                result = PyObject_IsTrue(PyObject_CallObject(self->onrightclick,
                Py_BuildValue("(O)", self)));
            break;
        case WE_FILESDROPPED:
            if( self->onfilesdropped ) {
                sl = (string_list*)data;
                arg = PyTuple_New(sl->count);
                for(i = 0; i < sl->count; i++)
                    PyTuple_SetItem(arg, i, PyUnicode_FromString(sl->strings[i]));
                PyObject_CallObject(self->onfilesdropped, Py_BuildValue("(OO)", self, arg));
            }
        case WE_RESTORE:
            if( self->onrestore )
                result = PyObject_IsTrue(PyObject_CallObject(self->onrestore,
                Py_BuildValue("(O)", self)));
            break;
        default:
            break;
    }
    PyGILState_Release(gs);
    return result;
}

static int window_on_npevent( window *w, NPEvent *e ) {
    WindowObject *self = (WindowObject *)system_window_get_private(w);
    if(self->f->f == NULL)
        return 0;
    return fl_dll->table.event(flashp_get_npp(self->f->f), e);
}

static PyObject *Window_new(PyTypeObject *type, PyObject *args) {
    static WindowObject *w;
    if (fl_dll == NULL) {
        PyErr_SetString(PyExc_Exception, "Application not initialized");
        return NULL;
    }
    w = (WindowObject *)type->tp_alloc(type, 0);
    return (PyObject *)w;
}

static int Window_init(WindowObject *self, PyObject *args, PyObject *kwds) {
    static char *kwlist[] = {"parent", "title", "width", "height", "flags", 0};
    const wchar_t *title = L"";
    int width = 400, height = 300, flags = WF_PLAIN;
    WindowObject *parent = (WindowObject *)Py_None;

    if (!PyArg_ParseTupleAndKeywords(args, kwds, "Ouiii|Ouiii", kwlist, &parent, &title, &width, &height, &flags))
        return -1;
    if (fl_dll == NULL)
        return -1;
    if ((PyObject *)parent != Py_None)
        self->w = system_window_create(parent->w, title, width, height, flags, window_on_event);
    else
        self->w = system_window_create(NULL, title, width, height, flags, window_on_event);
    if (self->w == NULL)
        return -1;
    system_window_set_private(self->w, (window_private_data *)self);
    system_window_set_npevent(self->w, window_on_npevent);
    self->f = (FlashObject *)PyObject_CallObject((PyObject *)&Flash_Type, Py_BuildValue("(O)", self));
    if (self->f == NULL)
        return -1;
    self->f->f = flashp_new(self->w);
    if (self->f->f == NULL )
        return -1;
    flashp_set_private(self->f->f, self->f);
    flashp_set_call(self->f->f, flash_on_call_event);
    // add the window to the list
    PyList_Append(windows, (PyObject *)self);
    return 0;
}

static void Window_dealloc(WindowObject *self) {
    Py_XDECREF(self->onclose);
    Py_XDECREF(self->ondestroy);
    Py_XDECREF(self->onfilesdropped);
    Py_XDECREF(self->onmaximize);
    Py_XDECREF(self->onminimize);
    Py_XDECREF(self->onrestore);
    Py_XDECREF(self->onrightclick);
    Py_TYPE(self)->tp_free((PyObject *)self);
}

static PyObject *Window_show(WindowObject *self) {
    system_window_show(self->w, 1);
    Py_RETURN_NONE;
}

static PyObject *Window_close(WindowObject *self) {
    system_window_destroy(self->w);
    Py_RETURN_NONE;
}

static PyObject *Window_hide(WindowObject *self) {
    system_window_show(self->w, 0);
    Py_RETURN_NONE;
}

static PyObject *Window_drag(WindowObject *self) {
    system_window_drag(self->w);
    Py_RETURN_NONE;
}

static PyObject *Window_resize(WindowObject *self, PyObject *args) {
    int o;
    if (!PyArg_ParseTuple(args, "i", &o))
        return NULL;
    system_window_resize(self->w, o);
    Py_RETURN_NONE;
}

static PyMethodDef Window_methods[] = {
    { "show", (PyCFunction)Window_show, METH_NOARGS, "" },
    { "hide", (PyCFunction)Window_hide, METH_NOARGS, "" },
    { "close", (PyCFunction)Window_close, METH_NOARGS, "" },
    { "drag", (PyCFunction)Window_drag, METH_NOARGS, "" },
    { "resize", (PyCFunction)Window_resize, METH_VARARGS, "" },
    { NULL, NULL } // Sentinel
};

static PyObject *Window_getattro(WindowObject *self, PyObject *name) {
    PyObject *res;
#if PY_MAJOR_VERSION >= 3
    char *n = _PyUnicode_AsString(name);
#else
    char *n = PyString_AsString(name);
    res = Py_FindMethod(Window_methods, (PyObject*) self, n);
    if (res)
        return res;
    PyErr_Clear();
#endif
    if (strcmp(n, "droptarget") == 0) 
        res = PyBool_FromLong(system_window_get_prop(self->w, WP_DROPTARGET));
    else if (strcmp(n, "flash") == 0)
        res = (PyObject *)self->f;
    else if (strcmp(n, "fullscreen") == 0) 
        res = PyBool_FromLong(system_window_get_prop(self->w, WP_FULLSCREEN));
    else if (strcmp(n, "handle") == 0)
        res = PyLong_FromLong((long)system_window_get_handle(self->w));
    else if (strcmp(n, "height") == 0) 
        res = PyLong_FromLong(system_window_get_prop(self->w, WP_HEIGHT));
    else if (strcmp(n, "left") == 0) 
        res = PyLong_FromLong(system_window_get_prop(self->w, WP_LEFT));
    else if (strcmp(n, "minimize_icon") == 0) 
        res = PyBool_FromLong(system_window_get_prop(self->w, WP_MINIMIZE_ICON));
    else if (strcmp(n, "maximize_icon") == 0) 
        res = PyBool_FromLong(system_window_get_prop(self->w, WP_MAXIMIZE_ICON));
    else if (strcmp(n, "minimized") == 0) 
        res = PyBool_FromLong(system_window_get_prop(self->w, WP_MINIMIZED));
    else if (strcmp(n, "maximized") == 0) 
        res = PyBool_FromLong(system_window_get_prop(self->w, WP_MAXIMIZED));
    else if (strcmp(n, "on_close") == 0) 
        res = self->onclose;
    else if (strcmp(n, "on_destroy") == 0) 
        res = self->ondestroy;
    else if (strcmp(n, "on_filesdropped") == 0)
        res = self->onfilesdropped;
    else if (strcmp(n, "on_maximize") == 0) 
        res = self->onmaximize;
    else if (strcmp(n, "on_minimize") == 0) 
        res = self->onminimize;
    else if (strcmp(n, "on_restore") == 0)
        res = self->onrestore;
    else if (strcmp(n, "on_rightclick") == 0) 
        res = self->onrightclick;
    else if (strcmp(n, "resizable") == 0) 
        res = PyBool_FromLong(system_window_get_prop(self->w, WP_RESIZABLE));
    else if (strcmp(n, "glassy") == 0) 
        res = PyBool_FromLong(system_window_get_prop(self->w, WP_GLASSY));
    else if (strcmp(n, "top") == 0) 
        res = PyLong_FromLong(system_window_get_prop(self->w, WP_TOP));
    else if (strcmp(n, "transparent") == 0) 
        res = PyBool_FromLong(system_window_get_prop(self->w, WP_TRANSPARENT));
    else if (strcmp(n, "width") == 0) 
        res = PyLong_FromLong(system_window_get_prop(self->w, WP_WIDTH));
    else {
        //PyErr_SetString(PyExc_AttributeError, name);
        //return NULL;
        return PyObject_GenericGetAttr(self, name);
    }
    Py_INCREF(res);
    return res;
}

static int Window_setattro(WindowObject *self, PyObject *name, PyObject *value) {
#if PY_MAJOR_VERSION >= 3
    char *n = _PyUnicode_AsString(name);
#else
    char *n = PyString_AsString(name);
#endif
    PyObject *f = value == Py_None ? NULL : value;

    if (strcmp(n, "title") == 0) {
        if (!PyUnicode_Check(value))
            return -1;
        system_window_set_title(self->w, PyUnicode_AsUnicode(value));
    }
    else if (strcmp(n, "resizable") == 0) {
        if (!PyBool_Check(value))
            return -1;
        system_window_set_prop(self->w, WP_RESIZABLE, PyObject_IsTrue(value));
    }
    else if (strcmp(n, "minimize_icon") == 0) {
        if (!PyBool_Check(value))
            return -1;
        system_window_set_prop(self->w, WP_MINIMIZE_ICON, PyObject_IsTrue(value));
    }
    else if (strcmp(n, "maximize_icon") == 0) {
        if (!PyBool_Check(value))
            return -1;
        system_window_set_prop(self->w, WP_MAXIMIZE_ICON, PyObject_IsTrue(value));
    }
    else if (strcmp(n, "minimized") == 0) {
        if (!PyBool_Check(value))
            return -1;
        system_window_set_prop(self->w, WP_MINIMIZED, PyObject_IsTrue(value));
    }
    else if (strcmp(n, "maximized") == 0) {
        if (!PyBool_Check(value))
            return -1;
        system_window_set_prop(self->w, WP_MAXIMIZED, PyObject_IsTrue(value));
    }
    else if (strcmp(n, "fullscreen") == 0) {
        if (!PyBool_Check(value))
            return -1;
        system_window_set_prop(self->w, WP_FULLSCREEN, PyObject_IsTrue(value));
    }
    else if (strcmp(n, "transparent") == 0) {
        if (!PyBool_Check(value))
            return -1;
        system_window_set_prop(self->w, WP_TRANSPARENT, PyObject_IsTrue(value));
    }
    else if (strcmp(n, "droptarget") == 0) {
        if (!PyBool_Check(value))
            return -1;
        system_window_set_prop(self->w, WP_DROPTARGET, PyObject_IsTrue(value));
    }
    else if (strcmp(n, "width") == 0) {
        if (!PyLong_Check(value))
            return -1;
        system_window_set_prop(self->w, WP_WIDTH, PyLong_AsLong(value));
    }
    else if (strcmp(n, "height") == 0) {
        if (!PyLong_Check(value))
            return -1;
        system_window_set_prop(self->w, WP_HEIGHT, PyLong_AsLong(value));
    }
    else if (strcmp(n, "left") == 0) {
        if (!PyLong_Check(value))
            return -1;
        system_window_set_prop(self->w, WP_LEFT, PyLong_AsLong(value));
    }
    else if (strcmp(n, "glassy") == 0) {
        if (!PyBool_Check(value))
            return -1;
        system_window_set_prop(self->w, WP_GLASSY, PyObject_IsTrue(value));
    }
    else if (strcmp(n, "top") == 0) {
        if (!PyLong_Check(value))
            return -1;
        system_window_set_prop(self->w, WP_TOP, PyLong_AsLong(value));
    }
    else if (strcmp(n, "on_destroy") == 0) {
        if (!PyCallable_Check(f))
            return -1;
        Py_XDECREF(self->ondestroy);
        self->ondestroy = f;
        Py_INCREF(self->ondestroy);
    }
    else if (strcmp(n, "on_close") == 0) {
        if (!PyCallable_Check(f))
            return -1;
        Py_XDECREF(self->onclose);
        self->onclose = f;
        Py_INCREF(self->onclose);
    }
    else if (strcmp(n, "on_minimize") == 0) {
        if (!PyCallable_Check(f))
            return -1;
        Py_XDECREF(self->onminimize);
        self->onminimize = f;
        Py_INCREF(self->onminimize);
    }
    else if (strcmp(n, "on_maximize") == 0) {
        if (!PyCallable_Check(f))
            return -1;
        Py_XDECREF(self->onmaximize);
        self->onmaximize = f;
        Py_INCREF(self->onmaximize);
    }
    else if (strcmp(n, "on_rightclick") == 0) {
        if (!PyCallable_Check(f))
            return -1;
        Py_XDECREF(self->onrightclick);
        self->onrightclick = f;
        Py_INCREF(self->onrightclick);
    }
    else if (strcmp(n, "on_filesdropped") == 0) {
        if (!PyCallable_Check(f))
            return -1;
        Py_XDECREF(self->onfilesdropped);
        self->onfilesdropped = f;
        Py_INCREF(self->onfilesdropped);
    }
    else if (strcmp(n, "on_restore") == 0) {
        if (!PyCallable_Check(f))
            return -1;
        Py_XDECREF(self->onrestore);
        self->onrestore = f;
        Py_INCREF(self->onrestore);
    }
    else
        return PyObject_GenericSetAttr((PyObject *)self, name, value);
    return 0;
}

static PyTypeObject Window_Type = {
    PyVarObject_HEAD_INIT(NULL, 0)
    "_swpy.Window",             		/* tp_name */
    sizeof(WindowObject),      			/* tp_basicsize */
    0,                          		/* tp_itemsize */
    (destructor)Window_dealloc, 		/* tp_dealloc */
    0,                          		/* tp_print */
    0,                          		/* tp_getattr */
    0,                          		/* tp_setattr */
    0,                          		/* tp_reserved */
    0,                          		/* tp_repr */
    0,                          		/* tp_as_number */
    0,                          		/* tp_as_sequence */
    0,                          		/* tp_as_mapping */
    0,                          		/* tp_hash */
    0,                          		/* tp_call */
    0,                          		/* tp_str */
    (getattrofunc)Window_getattro,		/* tp_getattro */
    (setattrofunc)Window_setattro,  	/* tp_setattro */
    0,                          		/* tp_as_buffer */
    Py_TPFLAGS_DEFAULT,                 /* tp_flags */
    PyDoc_STR("_swpy.Window object"),   /* tp_doc */
    0,                          		/* tp_traverse */
    0,                           		/* tp_clear */
    0,                          		/* tp_richcompare */
    0,                          		/* tp_weaklistoffset */
    0,                          		/* tp_iter */
    0,                          		/* tp_iternext */
    Window_methods,                		/* tp_methods */
    0,                          		/* tp_members */
    0,                          		/* tp_getset */
    0,                          		/* tp_base */
    0,                          		/* tp_dict */
    0,                          		/* tp_descr_get */
    0,                          		/* tp_descr_set */
    0,                          		/* tp_dictoffset */
    (initproc)Window_init,         		/* tp_init */
    (allocfunc)PyType_GenericAlloc, 	/* tp_alloc */
    (newfunc)Window_new,        		/* tp_new */
    (freefunc)PyObject_Del,       		/* tp_free */
};

/* ************************************************************************* */
// APPLICATION

typedef struct {
    PyObject_HEAD
} ApplicationObject;

static LRESULT CALLBACK app_sync_window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
    switch( msg ) {
        case SWM_INTERRUPT: {
            PyErr_SetInterrupt();
            return 0;
        }
        case SWM_SYNC_CALL: {
            return PyObject_CallObject((PyObject *)wparam/*sync_call_func*/, (PyObject *)lparam/*sync_call_args*/);
        }
    }
    return DefWindowProc(hwnd, msg, wparam, lparam);
}

// Application is a singleton
static PyObject *Application_new(PyTypeObject *type, PyObject *args) {
    if (application == NULL)
        application = (ApplicationObject *)type->tp_alloc(type, 0);
    else
        Py_XINCREF(application);
    return (PyObject *)application;
}

static int Application_init(ApplicationObject *self, PyObject *args, PyObject *kwds) {
    static char *kwlist[] = {"plugin_path", 0};
#if defined(SWPY_WINDOWS)
    wchar_t *plugin_path = L"NPSWF32.dll";
#elif defined(SWPY_LINUX)
    wchar_t *plugin_path = L"libflashplayer.so";
#endif
    char *error;

    _CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_DELAY_FREE_MEM_DF | _CRTDBG_LEAK_CHECK_DF );
    if (fl_dll != NULL) {
        PyErr_SetString(PyExc_Exception, "Already initialized");
        return -1;
    }
    if (!PyArg_ParseTupleAndKeywords(args, kwds, "u|u", kwlist, &plugin_path))
        return -1;
    fl_dll = dll_init(plugin_path, &error);
    if (fl_dll != NULL)
        system_init();
    else {
        PyErr_SetString(PyExc_Exception, error);
        return -1;
    }
    windows = PyList_New(0);
    if (windows == NULL)
        return -1;
#ifdef SWPY_WINDOWS
    {
        WNDCLASSEX wcl;
        HINSTANCE hinst = GetModuleHandle(NULL);
        memset(&wcl,0,sizeof(wcl));
        wcl.cbSize			= sizeof(WNDCLASSEX);
        wcl.style			= CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
        wcl.lpfnWndProc		= app_sync_window_proc;
        wcl.cbClsExtra		= 0;
        wcl.cbWndExtra		= 0;
        wcl.hInstance		= hinst;
        wcl.hIcon			= NULL;
        wcl.hCursor			= LoadCursor(NULL, IDC_ARROW);
        wcl.hbrBackground	= (HBRUSH)(COLOR_BTNFACE+1);
        wcl.lpszMenuName	= "";
        wcl.lpszClassName	= SYNC_CLASS_NAME;
        wcl.hIconSm			= 0;
        RegisterClassEx(&wcl);
        sync_call_hwnd = CreateWindow(SYNC_CLASS_NAME,"",0,0,0,0,0,NULL,NULL,NULL,NULL);
        InitializeCriticalSection(&sync_call_crit_sect);
        sync_call_event = CreateEvent(NULL, TRUE, TRUE, "SyncCallEvent");
        main_thread_id = GetCurrentThreadId();
    }
#endif
    return 0;
}

static void Application_dealloc(ApplicationObject *self) {
    Py_TYPE(self)->tp_free((PyObject *)self);
    application = NULL;
}

static PyObject *Application_main(PyObject *self) {
    Py_BEGIN_ALLOW_THREADS
    system_loop();
    //PyErr_SetInterrupt();
    Py_END_ALLOW_THREADS
    Py_RETURN_NONE;
}

static int quit(void *arg) {
    Py_Exit(0);
}

static PyObject *Application_exit(PyObject *self) {
    int i, size;
    PyGILState_STATE gs;
    system_exit_loop();
    //gs = PyGILState_Ensure();
    PyErr_SetInterrupt();//Py_AddPendingCall(&quit, NULL);
    //PyGILState_Release(gs);
    size = PyList_GET_SIZE(windows);
    for (i = 0; i < size; i++) {
        WindowObject *w = (WindowObject *)PyList_GetItem(windows, i);
        if (w != NULL) {
            system_window_destroy(w->w);
        }
    }
    Py_XDECREF(windows);
    windows = NULL;
    dll_close(fl_dll);
    fl_dll = NULL;
    system_cleanup();
    freeNPIds();
#ifdef SWPY_WINDOWS
    CloseHandle(sync_call_event);
    DeleteCriticalSection(&sync_call_crit_sect);
#endif
    Py_RETURN_NONE;
}

static PyObject *Application_sync_call(PyObject *self, PyObject *args) {
    PyObject *sync_func, *sync_func_args, *sync_result = NULL;
    PyGILState_STATE gs;
#ifdef SWPY_WINDOWS
    EnterCriticalSection(&sync_call_crit_sect);
    WaitForSingleObject(sync_call_event, INFINITE);
#endif
    gs = PyGILState_Ensure();
    //PyThreadState *ts = PyEval_SaveThread();
    if (!PyArg_ParseTuple(args, "OO", &sync_func, &sync_func_args))
        return NULL;
    if (!PyCallable_Check(sync_func))
        return NULL;
    if (!PyTuple_Check(sync_func_args))
        return NULL;
#ifdef SWPY_WINDOWS
    sync_result = (PyObject *)SendMessage(sync_call_hwnd, SWM_SYNC_CALL,
        (WPARAM)sync_func, (LPARAM)sync_func_args);
    PyGILState_Release(gs);
    LeaveCriticalSection(&sync_call_crit_sect);
#endif
    //PyEval_RestoreThread(ts);
    return sync_result;
}

static PyObject *Application_plugin_file_version(PyObject *self, PyObject *args) {
    wchar_t *path, *info;
    PyObject *v;

    if (!PyArg_ParseTuple(args, "u", &path)) {
        return NULL;
    }
    info = system_plugin_file_version(path);
    if( info != NULL ) {
        v = PyUnicode_FromUnicode(info, wcslen(info));
        free(info);
        return v;
    }
    Py_RETURN_NONE;
}

static PyObject *Application_get_windows(ApplicationObject *self) {
    return windows;
}

static PyMethodDef Application_methods[] = {
    { "main", (PyCFunction)Application_main, METH_NOARGS, "" },
    { "exit", (PyCFunction)Application_exit, METH_NOARGS, "" },
    { "sync_call", (PyCFunction)Application_sync_call, METH_VARARGS, "" },
    { "plugin_file_version", (PyCFunction)Application_plugin_file_version, METH_VARARGS, "" },
    { NULL, NULL } // Sentinel
};

static PyGetSetDef Application_getsets[] = {
    {"windows", (getter)Application_get_windows, (setter)NULL},
    {NULL}
};

static PyTypeObject Application_Type = {
    PyVarObject_HEAD_INIT(NULL, 0)
    "_swpy.Application",           			/* tp_name */
    sizeof(ApplicationObject),    			/* tp_basicsize */
    0,                          			/* tp_itemsize */
    (destructor)Application_dealloc,        /* tp_dealloc */
    0,                          			/* tp_print */
    0,                          			/* tp_getattr */
    0,                          			/* tp_setattr */
    0,                          			/* tp_reserved */
    0,                          			/* tp_repr */
    0,                          			/* tp_as_number */
    0,                          			/* tp_as_sequence */
    0,                          			/* tp_as_mapping */
    0,                          			/* tp_hash */
    0,                          			/* tp_call */
    0,                          			/* tp_str */
    0,                          			/* tp_getattro */
    0,                          			/* tp_setattro */
    0,                          			/* tp_as_buffer */
    Py_TPFLAGS_DEFAULT,                     /* tp_flags */
    PyDoc_STR("_swpy.Application object"),  /* tp_doc */
    0,                                   	/* tp_traverse */
    0,                          			/* tp_clear */
    0,                          			/* tp_richcompare */
    0,                          			/* tp_weaklistoffset */
    0,                          			/* tp_iter */
    0,                          			/* tp_iternext */
    Application_methods,           			/* tp_methods */
    0,                          			/* tp_members */
    Application_getsets,           			/* tp_getset */
    0,                          			/* tp_base */
    0,                          			/* tp_dict */
    0,                          			/* tp_descr_get */
    0,                          			/* tp_descr_set */
    0,                          			/* tp_dictoffset */
    (initproc)Application_init,   			/* tp_init */
    (allocfunc)PyType_GenericAlloc,			/* tp_alloc */
    (newfunc)Application_new,    			/* tp_new */
    (freefunc)PyObject_Del,    			/* tp_free */
};

/* ************************************************************************* */
// MODULE

#if PY_MAJOR_VERSION >= 3
static struct PyModuleDef _swpymodule = {
	PyModuleDef_HEAD_INIT,
	"_swpy",
	NULL,
	-1,
	NULL, 
	NULL,
	NULL,
	NULL,
	NULL
};
#endif

#if PY_MAJOR_VERSION >= 3
PyMODINIT_FUNC PyInit__swpy(void)
#else
PyMODINIT_FUNC init_swpy(void)
#endif
{
	PyObject *mod;

	if (PyType_Ready(&Application_Type) < 0)
		return NULL;
	if (PyType_Ready(&Window_Type) < 0)
		return NULL;
	if (PyType_Ready(&Flash_Type) < 0)
		return NULL;
	if (PyType_Ready(&Stream_Type) < 0)
		return NULL;

#if PY_MAJOR_VERSION >= 3
	mod = PyModule_Create(&_swpymodule);
#else
    mod = Py_InitModule3("_swpy", NULL, "_swpy");
#endif
	if (mod == NULL) {
		return NULL;
	}

	if (PyModule_AddObject(mod, "Application", (PyObject*)&Application_Type) < 0)
		return NULL;
	if (PyModule_AddObject(mod, "Window", (PyObject*)&Window_Type) < 0)
		return NULL;
#ifdef WITH_THREAD
	PyEval_InitThreads();
#endif
	if (PyErr_Occurred())
	{
		PyErr_SetString(PyExc_ImportError, "_swpy import failed");
		Py_DECREF(mod);
		mod = NULL;
	}
	return mod;
}